package com.yinsin.jpabatis.mapper;

import java.sql.ResultSet;

import com.yinsin.jpabatis.config.Configuration;
import com.yinsin.jpabatis.type.JdbcType;
import com.yinsin.jpabatis.type.TypeHandler;
import com.yinsin.jpabatis.type.TypeHandlerRegistry;

public class ParameterMapping {
	
	private Configuration configuration;
	private String property;
	private ParameterMode mode;
	private Class<?> javaType = Object.class;
	private JdbcType jdbcType;
	private Integer numericScale;
	private TypeHandler<?> typeHandler;
	private String resultMapId;
	private String jdbcTypeName;
	private String expression;

	private ParameterMapping() {
	}

	public static class Builder {
		private ParameterMapping parameterMapping = new ParameterMapping();

		public Builder(Configuration configuration, String property, TypeHandler<?> typeHandler) {
			parameterMapping.configuration = configuration;
			parameterMapping.property = property;
			parameterMapping.typeHandler = typeHandler;
			parameterMapping.mode = ParameterMode.IN;
		}

		public Builder(Configuration configuration, String property, Class<?> javaType) {
			parameterMapping.configuration = configuration;
			parameterMapping.property = property;
			parameterMapping.javaType = javaType;
			parameterMapping.mode = ParameterMode.IN;
		}

		public Builder mode(ParameterMode mode) {
			parameterMapping.mode = mode;
			return this;
		}

		public Builder javaType(Class<?> javaType) {
			parameterMapping.javaType = javaType;
			return this;
		}

		public Builder jdbcType(JdbcType jdbcType) {
			parameterMapping.jdbcType = jdbcType;
			return this;
		}

		public Builder numericScale(Integer numericScale) {
			parameterMapping.numericScale = numericScale;
			return this;
		}

		public Builder resultMapId(String resultMapId) {
			parameterMapping.resultMapId = resultMapId;
			return this;
		}

		public Builder typeHandler(TypeHandler<?> typeHandler) {
			parameterMapping.typeHandler = typeHandler;
			return this;
		}

		public Builder jdbcTypeName(String jdbcTypeName) {
			parameterMapping.jdbcTypeName = jdbcTypeName;
			return this;
		}

		public Builder expression(String expression) {
			parameterMapping.expression = expression;
			return this;
		}

		public ParameterMapping build() {
			resolveTypeHandler();
			validate();
			return parameterMapping;
		}

		private void validate() {
			if (ResultSet.class.equals(parameterMapping.javaType)) {
				if (parameterMapping.resultMapId == null) {
					throw new IllegalStateException("Missing resultmap in property '" + parameterMapping.property + "'.  "
							+ "Parameters of type java.sql.ResultSet require a resultmap.");
				}
			} else {
				if (parameterMapping.typeHandler == null) {
					throw new IllegalStateException("Type handler was null on parameter mapping for property '" + parameterMapping.property
							+ "'. It was either not specified and/or could not be found for the javaType (" + parameterMapping.javaType.getName() + ") : jdbcType ("
							+ parameterMapping.jdbcType + ") combination.");
				}
			}
		}

		private void resolveTypeHandler() {
			if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
				Configuration configuration = parameterMapping.configuration;
				TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
				parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
			}
		}

	}

	public String getProperty() {
		return property;
	}

	/**
	 * Used for handling output of callable statements
	 * 
	 * @return
	 */
	public ParameterMode getMode() {
		return mode;
	}

	/**
	 * Used for handling output of callable statements
	 * 
	 * @return
	 */
	public Class<?> getJavaType() {
		return javaType;
	}

	/**
	 * Used in the UnknownTypeHandler in case there is no handler for the
	 * property type
	 * 
	 * @return
	 */
	public JdbcType getJdbcType() {
		return jdbcType;
	}

	/**
	 * Used for handling output of callable statements
	 * 
	 * @return
	 */
	public Integer getNumericScale() {
		return numericScale;
	}

	/**
	 * Used when setting parameters to the PreparedStatement
	 * 
	 * @return
	 */
	public TypeHandler<?> getTypeHandler() {
		return typeHandler;
	}

	/**
	 * Used for handling output of callable statements
	 * 
	 * @return
	 */
	public String getResultMapId() {
		return resultMapId;
	}

	/**
	 * Used for handling output of callable statements
	 * 
	 * @return
	 */
	public String getJdbcTypeName() {
		return jdbcTypeName;
	}

	/**
	 * Not used
	 * 
	 * @return
	 */
	public String getExpression() {
		return expression;
	}

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("ParameterMapping{");
		// sb.append("configuration=").append(configuration); // configuration
		// doesn't have a useful .toString()
		sb.append("property='").append(property).append('\'');
		sb.append(", mode=").append(mode);
		sb.append(", javaType=").append(javaType);
		sb.append(", jdbcType=").append(jdbcType);
		sb.append(", numericScale=").append(numericScale);
		// sb.append(", typeHandler=").append(typeHandler); // typeHandler also
		// doesn't have a useful .toString()
		sb.append(", resultMapId='").append(resultMapId).append('\'');
		sb.append(", jdbcTypeName='").append(jdbcTypeName).append('\'');
		sb.append(", expression='").append(expression).append('\'');
		sb.append('}');
		return sb.toString();
	}
}
